package com.cwnu.sw.cys.core.common.aspect;

import cn.hutool.core.util.StrUtil;
import com.cwnu.sw.cys.core.common.annotation.RedisLock;
import com.cwnu.sw.cys.core.common.service.LockService;
import com.cwnu.sw.cys.core.common.utils.SpElUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 分布式锁注解切面类
 * <p>
 * 该类用于对带有@RedisLock注解的方法进行环绕通知，实现分布式锁的加锁和解锁操作。
 * </p>
 *
 * @author jisam
 * @version 1.0
 * @contact jisamchan@outlook.com
 * @date 25/03/2025
 *
 * Order(0) 确保比事务注解先执行，分布式锁在事务外
 */
@Slf4j
@Aspect
@Component
@Order(0)
public class RedisLockAspect {

    @Autowired
    private LockService lockService;

    /**
     * 环绕通知方法，用于处理带有@RedisLock注解的方法
     * <p>
     * 该方法会在目标方法执行前后进行分布式锁的加锁和解锁操作。
     * </p>
     *
     * @param joinPoint 连接点，包含目标方法的信息和参数
     * @return 目标方法的执行结果
     * @throws Throwable 如果目标方法执行过程中抛出异常，则直接抛出
     */
    @Around("@annotation(com.cwnu.sw.cys.core.common.annotation.RedisLock)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取目标方法
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        // 获取方法上的@RedisLock注解
        RedisLock redisLock = method.getAnnotation(RedisLock.class);
        // 如果注解中的prefix为空，则使用方法的限定名作为前缀，否则使用注解中的prefix
        String prefix = StrUtil.isBlank(redisLock.prefix()) ? SpElUtils.getMethodKey(method) : redisLock.prefix();
        // 解析SpEL表达式，生成最终的锁键
        String key = SpElUtils.parseSpEl(method, joinPoint.getArgs(), redisLock.key());
        // 执行分布式锁操作，并返回目标方法的执行结果
        return lockService.executeWithLockThrows(prefix + ":" + key, redisLock.waitTime(), redisLock.unit(), joinPoint::proceed);
    }
}
